| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2023 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use std::{process::exit, sync::Arc};
- use clap::{Parser, Subcommand};
- use log::{error, info};
- use simplelog::{ColorChoice, TermLogger, TerminalMode};
- use smol::Executor;
- use url::Url;
- use darkfi::{
- rpc::client::RpcClient,
- util::cli::{get_log_config, get_log_level},
- Result,
- };
- mod drawdown;
- mod filter;
- mod primitives;
- mod rpc;
- mod util;
- mod view;
- use drawdown::{drawdown, to_naivedate};
- use filter::{apply_filter, get_ids, no_filter_warn};
- use primitives::{task_from_cli, State, TaskEvent};
- use util::{due_as_timestamp, prompt_text};
- use view::{print_task_info, print_task_list};
- use taud::task_info::TaskInfo;
- const DEFAULT_PATH: &str = "~/tau_exported_tasks";
- #[derive(Parser)]
- #[clap(name = "tau", version)]
- #[clap(subcommand_precedence_over_arg = true)]
- struct Args {
- #[arg(short, action = clap::ArgAction::Count)]
- /// Increase verbosity (-vvv supported)
- verbose: u8,
- #[clap(short, long, default_value = "tcp://127.0.0.1:23330")]
- /// taud JSON-RPC endpoint
- endpoint: Url,
- /// Search filters (zero or more)
- filters: Vec<String>,
- #[clap(subcommand)]
- command: Option<TauSubcommand>,
- }
- #[derive(Subcommand)]
- enum TauSubcommand {
- /// Add a new task.
- ///
- /// Quick start:
- /// Adding a new task named "New task":
- /// tau add New task
- /// New task with description:
- /// tau add Add more info to tau desc:"some awesome description"
- /// New task with project and assignee:
- /// tau add Third task project:p2p Arusty
- /// Add a task with due date September 12th and rank of 4.6:
- /// tau add Task no. Four due:1209 rank:4.6
- ///
- /// Notice that if the command does not have "desc" key it will open
- /// an Editor so you can write the description there.
- ///
- /// Also note that "project" key can have multiple
- /// comma-separated values.
- /// "assign" on the other hand uses '@' character but also could be
- /// multiple values, but like so:
- /// @person1 @person2
- ///
- /// All keys example:
- /// tau add Improve CLI desc:"Description here" project:tau,darkirc @dave @rusty due:0210 rank:2.2
- ///
- #[clap(verbatim_doc_comment)]
- Add {
- /// Pairs of key:value (e.g. desc:description @dark).
- values: Vec<String>,
- },
- /// Modify/Edit an existing task.
- Modify {
- #[clap(allow_hyphen_values = true)]
- /// Values (e.g. project:blockchain).
- values: Vec<String>,
- },
- /// List tasks.
- List,
- /// Start task(s).
- Start,
- /// Open task(s).
- Open,
- /// Pause task(s).
- Pause,
- /// Stop task(s).
- Stop,
- /// Set or Get comment for task(s).
- Comment {
- /// Set comment content if provided (Get comments otherwise).
- content: Vec<String>,
- },
- /// Get all data about selected task(s).
- Info,
- /// Switch workspace.
- Switch {
- /// Tau workspace.
- workspace: String,
- },
- /// Import tasks from a specified directory.
- Import {
- /// The parent directory from where you want to import tasks.
- path: Option<String>,
- },
- /// Export tasks to a specified directory.
- Export {
- /// The parent directory to where you want to export tasks.
- path: Option<String>,
- },
- /// Log drawdown.
- Log {
- /// The month in which we want to draw a heatmap (e.g. 0822 for August 2022).
- month: Option<String>,
- /// The person of which we want to draw a heatmap
- /// (if not provided we list all assignees).
- assignee: Option<String>,
- },
- }
- pub struct Tau {
- pub rpc_client: RpcClient,
- }
- fn main() -> Result<()> {
- let args = Args::parse();
- let log_level = get_log_level(args.verbose);
- let log_config = get_log_config(args.verbose);
- TermLogger::init(log_level, log_config, TerminalMode::Mixed, ColorChoice::Auto)?;
- let executor = Arc::new(Executor::new());
- smol::block_on(executor.run(async {
- let rpc_client = RpcClient::new(args.endpoint, executor.clone()).await?;
- let tau = Tau { rpc_client };
- let mut filters = args.filters.clone();
- // If IDs are provided in filter we use them to get the tasks from the daemon
- // then remove IDs from filter so we can do apply_filter() normally.
- // If not provided we use get_ids() to get them from the daemon.
- let ids = get_ids(&mut filters)?;
- let ids_clone = ids.clone();
- let task_ids = if ids.is_empty() { tau.get_ids().await? } else { ids };
- let mut tasks = if filters.contains(&"state:stop".to_string()) ||
- filters.contains(&"all".to_string())
- {
- tau.get_stop_tasks(None).await?
- } else {
- vec![]
- };
- for id in task_ids {
- tasks.push(tau.get_task_by_id(id).await?);
- }
- if ids_clone.len() == 1 && args.command.is_none() {
- let tsk = tasks[0].clone();
- print_task_info(tsk)?;
- return Ok(())
- }
- for filter in filters {
- apply_filter(&mut tasks, &filter);
- }
- // Parse subcommands
- match args.command {
- Some(sc) => match sc {
- TauSubcommand::Add { values } => {
- let mut task = task_from_cli(values)?;
- if task.title.is_empty() {
- error!("Please provide a title for the task.");
- exit(1);
- };
- if task.desc.is_none() {
- task.desc = prompt_text(TaskInfo::from(task.clone()), "description")?;
- };
- if task.clone().desc.unwrap().trim().is_empty() {
- error!("Abort adding the task due to empty description.");
- exit(1)
- }
- let title = task.clone().title;
- let task_id = tau.add(task).await?;
- if task_id > 0 {
- println!("Created task {} \"{}\"", task_id, title);
- }
- Ok(())
- }
- TauSubcommand::Modify { values } => {
- if args.filters.is_empty() {
- no_filter_warn()
- }
- let base_task = task_from_cli(values)?;
- for task in tasks.clone() {
- let res = tau.update(task.id, base_task.clone()).await?;
- if res {
- let tsk = tau.get_task_by_id(task.id).await?;
- print_task_info(tsk)?;
- }
- }
- Ok(())
- }
- TauSubcommand::Start => {
- if args.filters.is_empty() {
- no_filter_warn()
- }
- let state = State::Start;
- for task in tasks {
- if tau.set_state(task.id, &state).await? {
- println!("Started task: {:?}", task.id);
- }
- }
- Ok(())
- }
- TauSubcommand::Open => {
- if args.filters.is_empty() {
- no_filter_warn()
- }
- let state = State::Open;
- for task in tasks {
- if tau.set_state(task.id, &state).await? {
- println!("Opened task: {:?}", task.id);
- }
- }
- Ok(())
- }
- TauSubcommand::Pause => {
- if args.filters.is_empty() {
- no_filter_warn()
- }
- let state = State::Pause;
- for task in tasks {
- if tau.set_state(task.id, &state).await? {
- println!("Paused task: {:?}", task.id);
- }
- }
- Ok(())
- }
- TauSubcommand::Stop => {
- if args.filters.is_empty() {
- no_filter_warn()
- }
- let state = State::Stop;
- for task in tasks {
- if tau.set_state(task.id, &state).await? {
- println!("Stopped task: {}", task.id);
- }
- }
- Ok(())
- }
- TauSubcommand::Comment { content } => {
- if args.filters.is_empty() {
- no_filter_warn()
- }
- for task in tasks {
- let comment = if content.is_empty() {
- prompt_text(task.clone(), "comment")?
- } else {
- Some(content.join(" "))
- };
- if comment.clone().unwrap().trim().is_empty() || comment.is_none() {
- error!("Abort due to empty comment.");
- exit(1)
- }
- let res = tau.set_comment(task.id, comment.unwrap().trim()).await?;
- if res {
- let tsk = tau.get_task_by_id(task.id).await?;
- print_task_info(tsk)?;
- }
- }
- Ok(())
- }
- TauSubcommand::Info => {
- for task in tasks {
- let task = tau.get_task_by_id(task.id).await?;
- print_task_info(task)?;
- }
- Ok(())
- }
- TauSubcommand::Switch { workspace } => {
- if tau.switch_ws(workspace.clone()).await? {
- println!("You are now on \"{}\" workspace", workspace);
- } else {
- println!("Workspace \"{}\" is not configured", workspace);
- }
- Ok(())
- }
- TauSubcommand::Export { path } => {
- let path = path.unwrap_or_else(|| DEFAULT_PATH.into());
- let res = tau.export_to(path.clone()).await?;
- if res {
- info!("Exported to {}", path);
- } else {
- error!("Error exporting to {}", path);
- }
- Ok(())
- }
- TauSubcommand::Import { path } => {
- let path = path.unwrap_or_else(|| DEFAULT_PATH.into());
- let res = tau.import_from(path.clone()).await?;
- if res {
- info!("Imported from {}", path);
- } else {
- error!("Error importing from {}", path);
- }
- Ok(())
- }
- TauSubcommand::Log { month, assignee } => {
- match month {
- Some(date) => {
- let ts = to_naivedate(date.clone())?
- .and_hms_opt(12, 0, 0)
- .unwrap()
- .timestamp();
- let tasks = tau.get_stop_tasks(Some(ts.try_into().unwrap())).await?;
- drawdown(date, tasks, assignee)?;
- }
- None => {
- let ws = tau.get_ws().await?;
- let tasks = tau.get_stop_tasks(None).await?;
- print_task_list(tasks, ws)?;
- }
- }
- Ok(())
- }
- TauSubcommand::List => {
- let ws = tau.get_ws().await?;
- print_task_list(tasks, ws)
- }
- },
- None => {
- let ws = tau.get_ws().await?;
- print_task_list(tasks, ws)
- }
- }?;
- tau.close_connection().await;
- Ok(())
- }))
- }
|