use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use std::fmt; use std::fs; use std::path::Path; #[derive(Parser)] #[command(name = "todo")] #[command(about = "A simple todo list manager")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { Add { task: String }, List, Complete { id: usize }, Remove { id: usize }, } #[derive(Serialize, Deserialize, Debug)] struct Task { id: usize, description: String, completed: bool, } #[derive(Serialize, Deserialize, Debug)] struct TodoList { tasks: Vec, next_id: usize, } #[derive(Debug)] enum TodoError { FileError(String), ParseError(String), TaskNotFound(usize), InvalidInput(String), } impl fmt::Display for TodoError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TodoError::FileError(msg) => write!(f, "File error: {}", msg), TodoError::ParseError(msg) => write!(f, "Parse error: {}", msg), TodoError::TaskNotFound(id) => write!(f, "Task {} not found", id), TodoError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg), } } } impl std::error::Error for TodoError {} impl TodoList { fn new() -> Self { TodoList { tasks: Vec::new(), next_id: 1, } } fn load_from_file(filename: &str) -> Result> { if Path::new(filename).exists() { let contents = fs::read_to_string(filename)?; let todo_list = serde_json::from_str(&contents)?; Ok(todo_list) } else { Ok(TodoList::new()) } } fn save_to_file(&self, filename: &str) -> Result<(), Box> { let json = serde_json::to_string_pretty(self)?; fs::write(filename, json)?; Ok(()) } fn add_task(&mut self, description: String) -> Result<(), TodoError> { if description.trim().is_empty() { return Err(TodoError::InvalidInput( "Task description cannot be empty".to_string(), )); } let task = Task { id: self.next_id, description, completed: false, }; self.tasks.push(task); self.next_id += 1; Ok(()) } fn complete_task(&mut self, id: usize) -> Result<(), TodoError> { match self.tasks.iter_mut().find(|task| task.id == id) { Some(task) => { task.completed = true; Ok(()) } None => Err(TodoError::TaskNotFound(id)), } } } fn main() { let cli = Cli::parse(); if let Err(e) = run_command(cli) { eprintln!("Error: {}", e); std::process::exit(1); } } fn run_command(cli: Cli) -> Result<(), TodoError> { let mut todo_list = TodoList::load_from_file("tasks.json").map_err(|e| TodoError::FileError(e.to_string()))?; match cli.command { Commands::Add { task } => { todo_list.add_task(task)?; println!("Task added successfully"); } Commands::Complete { id } => { todo_list.complete_task(id)?; println!("Task {} marked as complete", id); } _ => {} // Handle other commands } todo_list .save_to_file("todo.json") .map_err(|e| TodoError::FileError(e.to_string()))?; Ok(()) }