#![deny(clippy::unwrap_used, clippy::expect_used)] use std::fs; use clap::{Parser, Subcommand}; use client::fetch::{add_todo, fetch_categories, fetch_tags}; use color_eyre::eyre; use directories::ProjectDirs; use serde::{Deserialize, Serialize}; use types::{todo::AddTodoModel, todo::CreateTodoData}; #[derive(Debug, Parser)] #[command(name = "task-line")] #[command(about = "The TaskLine CLI", long_about = None)] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Debug, Subcommand)] enum Commands { Settings { #[arg(short, long)] url: Option, #[arg(short = 'k', long)] api_key: Option, }, New { title: String, note: Option, #[arg(short, long)] category: Option, #[arg(short, long)] tags: Vec, }, } #[tokio::main] async fn main() -> eyre::Result<()> { let args = Cli::parse(); match args.command { Commands::Settings { url: None, api_key: None, } => { let settings = Settings::read()?; if let Some(settings) = settings { println!("current settings\n{settings:?}"); } else { println!("no settings set"); } } Commands::Settings { url, api_key } => { let mut settings = Settings::read()?.unwrap_or_default(); if let Some(url) = url { settings.url = Some(url); } if let Some(api_key) = api_key { settings.api_key = Some(api_key); } settings.save()?; } Commands::New { title, note, category, tags, } => { let settings = Settings::read()?; let Some(settings) = settings else { return Err(eyre::eyre!("no settings set")); }; let Some(url) = settings.url.as_deref() else { return Err(eyre::eyre!("no url set")); }; let Some(token) = settings.api_key.as_deref() else { return Err(eyre::eyre!("no api key set")); }; let server_tags = fetch_tags(url, token).await?; let server_categories = fetch_categories(url, token).await?; let tags = server_tags .iter() .filter(|tag| tags.contains(&tag.name)) .map(|tag| tag.uuid) .collect::>(); let categories = server_categories .iter() .filter(|cat| category == Some(cat.name.clone())) .map(|cat| cat.uuid) .next(); add_todo( url, token, &AddTodoModel { data: CreateTodoData { uuid: uuid::Uuid::new_v4(), title, note: note.unwrap_or_default(), tags: Some(tags), category: categories, time: None, reminders: None, }, position: None, previous_id: None, }, ) .await?; } } Ok(()) } #[derive(Debug, Default, Deserialize, Serialize)] struct Settings { url: Option, api_key: Option, } impl Settings { fn read() -> eyre::Result> { let project_dirs = ProjectDirs::from("dev", "tobinio", "TaskLine") .ok_or(eyre::eyre!("Failed to create Project dir"))?; let settings_path = project_dirs.config_dir().join("settings.json"); if !settings_path.exists() { return Ok(None); } let content = fs::read_to_string(&settings_path)?; Ok(Some(serde_json::from_str(&content)?)) } fn save(&self) -> eyre::Result<()> { let project_dirs = ProjectDirs::from("dev", "tobinio", "TaskLine") .ok_or(eyre::eyre!("Failed to create Project dir"))?; let settings_path = project_dirs.config_dir().join("settings.json"); if let Some(parent) = settings_path.parent() { fs::create_dir_all(parent)?; } fs::write(&settings_path, serde_json::to_string(self)?)?; Ok(()) } }