141 lines
3.4 KiB
Rust
141 lines
3.4 KiB
Rust
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<Task>,
|
|
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<Self, Box<dyn std::error::Error>> {
|
|
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<dyn std::error::Error>> {
|
|
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(())
|
|
}
|