Browse Source

bin/tau: allow more filters and use structopt for taucli

Dastan-glitch 4 years ago
parent
commit
eb25d9a382
3 changed files with 67 additions and 73 deletions
  1. 2 0
      bin/tau/tau-cli/Cargo.toml
  2. 13 21
      bin/tau/tau-cli/src/main.rs
  3. 52 52
      bin/tau/tau-cli/src/util.rs

+ 2 - 0
bin/tau/tau-cli/Cargo.toml

@@ -26,3 +26,5 @@ prettytable-rs = "0.8.0"
 # Encoding and parsing
 serde_json = "1.0.79"
 serde = {version = "1.0.136", features = ["derive"]}
+structopt = "0.3.26"
+structopt-toml = "0.5.0"

+ 13 - 21
bin/tau/tau-cli/src/main.rs

@@ -1,4 +1,3 @@
-use clap::{CommandFactory, Parser};
 use log::error;
 use prettytable::{cell, format, row, table};
 use serde_json::{json, Value};
@@ -6,11 +5,12 @@ use simplelog::{ColorChoice, TermLogger, TerminalMode};
 
 use darkfi::{
     util::{
-        cli::{log_config, spawn_config, Config},
+        cli::{log_config, spawn_config},
         path::get_config_path,
     },
     Result,
 };
+use structopt_toml::StructOptToml;
 
 mod jsonrpc;
 mod util;
@@ -19,17 +19,17 @@ 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, TauConfig,
+        set_title, timestamp_to_date, CliTau, CliTauSubCommands, TaskInfo, CONFIG_FILE,
         CONFIG_FILE_CONTENTS,
     },
 };
 
-async fn start(options: CliTau, config: TauConfig) -> Result<()> {
-    let rpc_addr = &format!("tcp://{}", &config.rpc_listen.clone());
+async fn start(options: CliTau) -> Result<()> {
+    let rpc_addr = &format!("tcp://{}", &options.rpc_listen.clone());
 
-    if !options.filter.is_empty() {
+    if !options.filters.is_empty() {
         let rep = list(rpc_addr, json!([])).await?;
-        list_tasks(rep, options.filter)?;
+        list_tasks(rep, options.filters)?;
     } else {
         match options.command {
             Some(CliTauSubCommands::Add { title, desc, assign, project, due, rank }) => {
@@ -157,21 +157,13 @@ async fn start(options: CliTau, config: TauConfig) -> Result<()> {
 
 #[async_std::main]
 async fn main() -> Result<()> {
-    let args = CliTau::parse();
-    let matches = CliTau::command().get_matches();
-    let verbosity_level = matches.occurrences_of("verbose");
+    let args = CliTau::from_args_with_toml("").unwrap();
+    let cfg_path = get_config_path(args.config, CONFIG_FILE)?;
+    spawn_config(&cfg_path, CONFIG_FILE_CONTENTS.as_bytes())?;
+    let args = CliTau::from_args_with_toml(&std::fs::read_to_string(cfg_path)?).unwrap();
 
-    let (lvl, conf) = log_config(verbosity_level)?;
+    let (lvl, conf) = log_config(args.verbose.into())?;
     TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
 
-    let config_path = get_config_path(args.config.clone(), "taud_config.toml")?;
-
-    spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
-
-    let config: TauConfig = match Config::<TauConfig>::load(config_path) {
-        Ok(c) => c,
-        Err(_) => TauConfig::default(),
-    };
-
-    start(args, config).await
+    start(args).await
 }

+ 52 - 52
bin/tau/tau-cli/src/util.rs

@@ -2,35 +2,26 @@ use std::{
     env::{temp_dir, var},
     fs::{self, File},
     io::{self, Read, Write},
-    net::{IpAddr, Ipv4Addr, SocketAddr},
+    net::SocketAddr,
     ops::Index,
     process::Command,
 };
 
 use chrono::{Datelike, Local, NaiveDate, NaiveDateTime};
-use clap::{Parser, Subcommand};
+use clap::Subcommand;
 use log::error;
 use prettytable::{cell, format, row, Cell, Row, Table};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 
 use darkfi::{Error, Result};
+use structopt::StructOpt;
+use structopt_toml::StructOptToml;
 
-pub const CONFIG_FILE_CONTENTS: &[u8] = include_bytes!("../../taud_config.toml");
+pub const CONFIG_FILE: &str = "taud_config.toml";
+pub const CONFIG_FILE_CONTENTS: &str = include_str!("../../taud_config.toml");
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct TauConfig {
-    /// JSON-RPC listen URL
-    pub rpc_listen: SocketAddr,
-}
-
-impl Default for TauConfig {
-    fn default() -> Self {
-        Self { rpc_listen: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 11055) }
-    }
-}
-
-#[derive(Subcommand)]
+#[derive(Subcommand, Deserialize, Debug, StructOpt)]
 pub enum CliTauSubCommands {
     /// Add a new task
     Add {
@@ -113,21 +104,24 @@ pub struct TaskInfo {
 }
 
 /// Tau cli
-#[derive(Parser)]
-#[clap(name = "tau")]
-#[clap(author, version, about)]
+#[derive(Debug, Deserialize, StructOpt, StructOptToml)]
+#[serde(default)]
+#[structopt(name = "tau")]
 pub struct CliTau {
     /// Increase verbosity
-    #[clap(short, parse(from_occurrences))]
+    #[structopt(short, parse(from_occurrences))]
     pub verbose: u8,
+    /// JSON-RPC listen URL
+    #[structopt(long = "rpc", default_value = "127.0.0.1:11055")]
+    pub rpc_listen: SocketAddr,
     /// Sets a custom config file
-    #[clap(short, long)]
+    #[structopt(short, long)]
     pub config: Option<String>,
-    #[clap(subcommand)]
+    #[structopt(subcommand)]
     pub command: Option<CliTauSubCommands>,
-    #[clap(multiple_values = true)]
+    #[structopt(multiple = true)]
     /// Search criteria (zero or more)
-    pub filter: Vec<String>,
+    pub filters: Vec<String>,
 }
 
 pub fn due_as_timestamp(due: &str) -> Option<i64> {
@@ -277,19 +271,25 @@ pub fn get_from_task(task: Value, value: &str) -> Result<String> {
     Ok(result)
 }
 
-fn sort_and_filter(tasks: Vec<Value>, filter: Option<String>) -> Result<Vec<Value>> {
+fn filter_tasks(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() {
+    let 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 events = match task["events"].as_array() {
+                    Some(t) => t.to_owned(),
+                    None => {
+                        error!("Value is not an array!");
+                        vec![]
+                    }
+                };
 
                 let state = match events.last() {
                     Some(s) => s["action"].as_str().unwrap(),
@@ -302,7 +302,13 @@ fn sort_and_filter(tasks: Vec<Value>, filter: Option<String>) -> Result<Vec<Valu
         "pause" => tasks
             .into_iter()
             .filter(|task| {
-                let events = task["events"].as_array().unwrap().to_owned();
+                let events = match task["events"].as_array() {
+                    Some(t) => t.to_owned(),
+                    None => {
+                        error!("Value is not an array!");
+                        vec![]
+                    }
+                };
 
                 let state = match events.last() {
                     Some(s) => s["action"].as_str().unwrap(),
@@ -330,12 +336,16 @@ fn sort_and_filter(tasks: Vec<Value>, filter: Option<String>) -> Result<Vec<Valu
             tasks
                 .into_iter()
                 .filter(|task| {
-                    task[key]
-                        .as_array()
-                        .unwrap()
-                        .iter()
-                        .map(|s| s.as_str().unwrap())
-                        .any(|x| x == value)
+                    match task[key].as_array() {
+                        Some(t) => t.to_owned(),
+                        None => {
+                            error!("Value is not an array!");
+                            vec![]
+                        }
+                    }
+                    .iter()
+                    .map(|s| s.as_str().unwrap())
+                    .any(|x| x == value)
                 })
                 .collect()
         }
@@ -365,32 +375,22 @@ fn sort_and_filter(tasks: Vec<Value>, filter: Option<String>) -> Result<Vec<Valu
         _ => 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<()> {
+pub fn list_tasks(rep: Value, filters: 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)?;
+    let mut 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)?,
-    };
+    for filter in filters {
+        let temp = tasks;
+        tasks = filter_tasks(temp, Some(filter))?;
+    }
+
+    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() {
         (