//! Copyright (C) 2022, Dmitriy Pleshevskiy //! //! tas is free software: you can redistribute it and/or modify //! it under the terms of the GNU General Public License as published by //! the Free Software Foundation, either version 3 of the License, or //! (at your option) any later version. //! //! tas 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 General Public License for more details. //! //! You should have received a copy of the GNU General Public License //! along with tas. If not, see . //! //--------------------------------------------------------------------- // Rustc lints #![deny( unused_imports, unused_qualifications, rust_2018_idioms, unsafe_code, non_ascii_idents )] // Clippy lints #![deny(clippy::all)] //--------------------------------------------------------------------- use clap::Parser; use serde::{Deserialize, Serialize}; use std::{ cmp::Ordering, io::{BufRead, Write}, }; use xdg::BaseDirectories; mod cli; fn main() { let args = cli::Args::parse(); let xdg_dirs = BaseDirectories::with_prefix(env!("CARGO_PKG_NAME")).unwrap(); let finished_tasks_file_path = xdg_dirs.place_data_file("finished_data.json").unwrap(); let tasks_file_path = xdg_dirs.place_data_file("data.json").unwrap(); let mut tasks: Vec = std::fs::File::open(&tasks_file_path) .map(|file| serde_json::from_reader(file).unwrap()) .unwrap_or_default(); let current_task_info_file_path = xdg_dirs.place_data_file("current.json").unwrap(); let mut current_task_info: Option = std::fs::File::open(¤t_task_info_file_path) .map(|file| serde_json::from_reader(file).unwrap()) .unwrap_or_default(); match args.command { cli::SubCommand::Add { link, name } => { tasks.push(Task { name, link }); let mut file = std::fs::File::create(&tasks_file_path).unwrap(); file.write_all(&serde_json::to_vec(&tasks).unwrap()) .unwrap(); println!("added"); } cli::SubCommand::Edit { idx, name, link, no_link, } => { if current_task_info.is_some() { panic!("You can edit task only when you don't have an active task, yet"); } if idx == 0 || idx > tasks.len() { panic!("invalid index"); } let mut task = &mut tasks[idx - 1]; if let Some(name) = name { task.name = name; } if let Some(link) = link { task.link = Some(link); } else if no_link { task.link = None; } let mut file = std::fs::File::create(&tasks_file_path).unwrap(); file.write_all(&serde_json::to_vec(&tasks).unwrap()) .unwrap(); println!("changed"); } cli::SubCommand::Remove { idx } => { if current_task_info.is_some() { panic!("You can remove task only when you don't have an active task, yet"); } if idx == 0 || idx > tasks.len() { panic!("invalid index"); } let task = &tasks[idx - 1]; println!("You are deleting task:"); println!(" {}", task.name); if let Some(ref link) = task.link { println!(" link: {}", link); } println!("In most cases you need to `finish` command"); loop { print!("Do you still want to delete the task? (y/N): "); std::io::stdout().flush().unwrap(); let mut stdin = std::io::stdin().lock(); let mut buf = String::new(); stdin.read_line(&mut buf).unwrap(); match buf.chars().next().unwrap_or_default() { '\r' | '\n' | 'n' | 'N' => return, 'y' | 'Y' => break, _ => println!("Unrecognised answer. Please try again."), } } tasks.remove(idx - 1); let mut file = std::fs::File::create(&tasks_file_path).unwrap(); file.write_all(&serde_json::to_vec(&tasks).unwrap()) .unwrap(); println!("removed"); } cli::SubCommand::List => { for (i, task) in tasks.iter().enumerate() { let idx = i + 1; match current_task_info { Some(CurrentTaskInfo { task_idx, .. }) if task_idx == idx => print!("> "), _ => print!(" "), } print!("{}. ", idx); if task.link.is_some() { print!("(link) "); } println!("{}", task.name); } } cli::SubCommand::Priority { idx: target_idx, priority, } => { if current_task_info.is_some() { panic!("You can change priority only when you don't have an active task, yet"); } if target_idx == 0 || target_idx > tasks.len() { panic!("invalid index"); } let idx = match priority { cli::Priority::Before { idx } | cli::Priority::After { idx } => { match target_idx.cmp(&idx) { Ordering::Equal => return, Ordering::Less => idx - 1, Ordering::Greater => idx, } } }; if idx == 0 || idx > tasks.len() { panic!("invalid index"); } let target = tasks.remove(target_idx - 1); match priority { cli::Priority::Before { .. } => { tasks.insert(idx - 1, target); } cli::Priority::After { .. } => { tasks.insert(idx, target); } } let mut file = std::fs::File::create(&tasks_file_path).unwrap(); file.write_all(&serde_json::to_vec(&tasks).unwrap()) .unwrap(); } cli::SubCommand::Start { idx, open } => { let idx = idx.unwrap_or(1); if idx == 0 || idx > tasks.len() { panic!("invalid index"); } let task = &tasks[idx - 1]; current_task_info.replace(CurrentTaskInfo { task_idx: idx, task: task.clone(), }); let mut file = std::fs::File::create(¤t_task_info_file_path).unwrap(); file.write_all(&serde_json::to_vec(¤t_task_info).unwrap()) .unwrap(); println!("started"); if let (Some(link), true) = (task.link.as_ref(), open) { log::debug!("opening link..."); std::process::Command::new("xdg-open") .arg(link) .spawn() .expect("failed to start"); log::debug!("opened"); } /* println!("starting..."); println!("-- on start hook found"); println!("-- running..."); println!("> curl ..."); println!("-- status was changed to \"In progress\" successfuly"); */ } cli::SubCommand::Pause => { if current_task_info.take().is_none() { panic!("You can use the pause subcommand only when you have an active task"); } let mut file = std::fs::File::create(¤t_task_info_file_path).unwrap(); file.write_all(&serde_json::to_vec(¤t_task_info).unwrap()) .unwrap(); println!("paused"); } cli::SubCommand::Finish => match current_task_info.take() { None => { panic!("You can use the finish subcommand only when you have an active task") } Some(info) => { let mut finished_tasks: Vec = std::fs::File::open(&finished_tasks_file_path) .map(|file| serde_json::from_reader(file).unwrap()) .unwrap_or_default(); let task = tasks.remove(info.task_idx - 1); finished_tasks.push(task); let mut file = std::fs::File::create(&tasks_file_path).unwrap(); file.write_all(&serde_json::to_vec(&tasks).unwrap()) .unwrap(); let mut file = std::fs::File::create(&finished_tasks_file_path).unwrap(); file.write_all(&serde_json::to_vec(&finished_tasks).unwrap()) .unwrap(); let mut file = std::fs::File::create(¤t_task_info_file_path).unwrap(); file.write_all(&serde_json::to_vec(¤t_task_info).unwrap()) .unwrap(); println!("finished"); } }, cli::SubCommand::Status => match current_task_info { None => { panic!("You don't have an active task."); } Some(info) => { println!("Information about your current task:"); println!(" {}", info.task.name); if let Some(link) = info.task.link { println!(" link: {}", link); } } }, } } #[derive(Deserialize, Serialize, Clone)] struct Task { name: String, link: Option, // created_at } #[derive(Deserialize, Serialize)] struct CurrentTaskInfo { task_idx: usize, task: Task, // started_at }